package com.zxmzy.scaffold.http.interceptor

import android.text.TextUtils
import com.zxmzy.scaffold.http.tools.OkLog
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.Request
import okhttp3.Response
import okhttp3.internal.http.StatusLine.Companion.HTTP_CONTINUE
import okio.Buffer
import okio.GzipSource
import okio.source
import java.io.IOException
import java.net.HttpURLConnection.HTTP_NOT_MODIFIED
import java.net.HttpURLConnection.HTTP_NO_CONTENT
import java.nio.charset.Charset
import java.nio.charset.UnsupportedCharsetException
import java.util.concurrent.TimeUnit


/**
 * @author zhangxiaoming
 * @date 2023/8/25
 * @description
 */
class LoggingInterceptor : Interceptor {
    companion object {
        private val UTF8: Charset = Charset.forName("UTF-8")
    }

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        printRequest(chain, request)

        val startNs: Long = System.nanoTime()
        val response = chain.proceed(request)
        val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)

        printResponse(response, tookMs)
        return response
    }

    private fun printRequest(chain: Interceptor.Chain, request: Request) {
        val requestBody = request.body
        OkLog.start("Request ↓↓↓")
        OkLog.log("Method-->${request.method}")
        OkLog.log("URL-->${request.url}")
        val requestHeaders: Headers = request.headers
        if (requestHeaders.size > 0) {
            OkLog.log("_________________________ headers start _____________________________")
            for (i in 0 until requestHeaders.size) {
                val name = requestHeaders.name(i)
                if (!"Content-Type".equals(name, true)
                    && !"Content-Length".equals(name, true)
                ) {
                    OkLog.log("Headers-->${name}:${requestHeaders.value(i)}")
                }
            }
            OkLog.log("_________________________ headers end _____________________________")
        }
        requestBody?.let { it ->
            it.contentType()?.let { contentType ->
                OkLog.log("Content-Type-->$contentType")
            }
            if (it.contentLength() != -1L) {
                OkLog.log("Content-Length-->${it.contentLength()}")
            }
            if ("POST" == request.method) {
                val sb = StringBuilder()
                if (it is FormBody) {
                    for (i in 0 until it.size) {
                        sb.append(it.encodedName(i) + "=" + it.encodedValue(i) + ",")
                    }
                    OkLog.log("RequestBody:{$sb}")
                }
            }
            var buffer = Buffer()
            it.writeTo(buffer)
            var charset: Charset = UTF8
            val contentType = it.contentType()
            contentType?.charset(UTF8)?.let { cs -> charset = cs }
            if (it.contentLength().toInt() != 0
                && it.contentLength() < 32 * 1024
                && bodyIsText(contentType)
            ) {
                if (bodyEncodedGzip(request.headers)) {
                    buffer = decodeGzip(buffer);
                }
                OkLog.log("request body-->" + buffer.readString(charset));
            }
        }
        OkLog.end("Request ↑↑↑ ");
    }

    private fun printResponse(response: Response, tookMs: Long) {
        val responseBody = response.body
        val contentLength = responseBody?.contentLength()

        OkLog.start("Response ↓↓↓")
        OkLog.log("URL-->${response.request.url}")
        OkLog.log("Code-->${response.code}")
        OkLog.log("Message-->${response.message}")
        OkLog.log("TimeToken-->${tookMs}ms")
        OkLog.log("contentLength-->${contentLength}byte")
        OkLog.log("Method-->${response.request.method}")

        val responseHeaders = response.headers
        if (responseHeaders.size > 0) {
            OkLog.log("_________________________ headers start _____________________________")
            for (i in 0 until responseHeaders.size) {
                OkLog.log("${responseHeaders.name(i)}: ${responseHeaders.value(i)}")
            }
            OkLog.log("_________________________ headers end _____________________________")
        }
        if (hasBody(response)) {
            val source = responseBody?.source()
            source?.let {
                it.request(Long.MAX_VALUE)
                var buffer = it.buffer

                var charset = UTF8
                val contentType = responseBody.contentType()
                if (contentType != null) {
                    OkLog.log("ContentType-->$contentType")
                    try {
                        contentType.charset(UTF8)?.also { cs -> charset = cs }
                    } catch (e: UnsupportedCharsetException) {
                        OkLog.end("Response ↑↑↑")
                        return
                    }
                }

                if (contentLength != null) {
                    if (contentLength != 0L && contentLength < 32 * 1024 && bodyIsText(contentType)) {
                        if (bodyEncodedGzip(response.headers)) {
                            buffer = decodeGzip(buffer)
                        }
                        OkLog.log("_________________________ ResponseBody _________________________")
                        val str = buffer.clone().readString(charset)
                        OkLog.log(str)
                    }
                }
                OkLog.log("ResponseBody-->${buffer.size ?: 0}byte")
            }
        }
        OkLog.end("Response ↑↑↑")
    }

    private fun bodyEncodedGzip(headers: Headers): Boolean {
        val contentEncoding = headers["Content-Encoding"]
        return contentEncoding != null && contentEncoding.equals("gzip", ignoreCase = true)
    }

    private fun bodyIsText(contentType: MediaType?): Boolean {
        return contentType != null
                && ("text" == contentType.type || "json" == contentType.subtype || contentType.subtype.contains(
            "form"
        ))
    }

    @Throws(IOException::class)
    private fun decodeGzip(buffer: Buffer): Buffer {
        val gzipSource = GzipSource(buffer.clone().inputStream().source())
        val count = buffer.size
        val resultBuffer = Buffer()
        gzipSource.read(resultBuffer, count)
        gzipSource.close()
        return resultBuffer
    }

    private fun hasBody(response: Response): Boolean {
        if (response.request.method == "HEAD") {
            return false
        }
        val responseCode = response.code
        return if ((responseCode < HTTP_CONTINUE || responseCode >= 200) && responseCode != HTTP_NO_CONTENT && responseCode != HTTP_NOT_MODIFIED) {
            true
        } else contentLength(response.headers) != -1L
                || "chunked".equals(response.header("Transfer-Encoding"), ignoreCase = true)
    }

    private fun contentLength(headers: Headers): Long {
        var length = headers["Content-Length"]
        if (TextUtils.isEmpty(length)) {
            return -1
        }
        length = length!!.trim { it <= ' ' }
        return try {
            length.toLong()
        } catch (e: NumberFormatException) {
            -1
        }
    }
}
